package edu.zju.gis.dldsj.utils;

import edu.zju.gis.dldsj.type.DataType;
import edu.zju.gis.dldsj.type.Dataset;
import edu.zju.gis.dldsj.type.Row;
import edu.zju.gis.dldsj.type.StructType;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author yanlo yanlong_lee@qq.com
 * @version 1.0 2018/07/15
 * Comments 用于读取符号分隔的结构化文本文件，如TSV，CSV
 */
public class DSVHelper implements Closeable {
    private Delimiter delimiter;
    private boolean hasHeader;
    private BufferedReader reader;
    private StructType structType;
    private int offset;

    /**
     * @param url       文件位置
     * @param hasHeader 是否具有列名
     */
    public DSVHelper(String url, boolean hasHeader) {
        this(url, Delimiter.Comma, hasHeader, null);
    }

    /**
     * @param url       文件位置
     * @param hasHeader 是否具有列名
     * @param charset   文件编码字符集
     */
    public DSVHelper(String url, boolean hasHeader, Charset charset) {
        this(url, Delimiter.Comma, hasHeader, charset);
    }

    /**
     * @param url       文件位置
     * @param delimiter 字段分隔符，默认为英文逗号“,”
     * @param hasHeader 是否具有列名
     */
    public DSVHelper(String url, Delimiter delimiter, boolean hasHeader) {
        this(url, delimiter, hasHeader, null);
    }

    /**
     * @param url       文件位置
     * @param delimiter 字段分隔符，默认为英文逗号“,”
     * @param hasHeader 是否具有列名
     * @param charset   文件编码字符集，默认为UTF-8
     */
    public DSVHelper(String url, Delimiter delimiter, boolean hasHeader, Charset charset) {
        this.delimiter = delimiter;
        this.hasHeader = hasHeader;
        try {
            this.reader = new BufferedReader(new InputStreamReader(new FileInputStream(url), charset == null ? StandardCharsets.UTF_8 : charset));
            if (hasHeader) {
                String line = reader.readLine();
                if (line != null && !line.isEmpty()) {
                    structType = new StructType();
                    String[] fields = line.split(delimiter.value);
                    for (String field : fields)
                        structType.addField(field, DataType.STRING, false);
                } else
                    this.hasHeader = false;
            }
//            this.reader.mark((int)(new File(this.url)).length());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public BufferedReader getReader() {
        return reader;
    }

    public StructType getStructType() {
        return structType;
    }

    public void close() throws IOException {
        reader.close();
    }

    public Dataset<Row> load(int offset, int size) throws IOException {
        if (offset < this.offset) {
            throw new UnsupportedOperationException("暂不支持文件重读");
//            reader.reset();
//            while (offset-- > 0) {
//                if ((reader.readLine()) == null)
//                    return null;
//            }
//            this.offset = offset;
        } else if (offset > this.offset) {
            while ((offset - ++this.offset) >= 0) {
                if ((reader.readLine()) == null)
                    return null;
            }
        }
        int i = 0;
        String line;
        List<Row> items = new ArrayList<>();
        Dataset<Row> dataset = new Dataset<>(structType, items);
        int fieldCount = hasHeader ? structType.getFieldCount() : -1;
        while (i < size && (line = reader.readLine()) != null) {
            i++;
            this.offset++;
            String[] values = line.split(delimiter.value, -1);
            if (fieldCount < 0)
                fieldCount = values.length;
            if (values.length < fieldCount) {
                String[] tmp = new String[fieldCount];
                System.arraycopy(values, 0, tmp, 0, values.length);
                Arrays.fill(tmp, values.length, tmp.length, "");
                values = tmp;
            }
            if (hasHeader)
                items.add(new Row(structType, values));
            else
                items.add(new Row(values));
        }
        return dataset;
    }

    public enum Delimiter {
        Comma(","),
        Tab("\t"),
        VerticalBar("|");
        String value;

        Delimiter(String s) {
            this.value = s;
        }

        public String getValue() {
            return value;
        }
    }
}
